﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RestSharp;
using Newtonsoft.Json.Linq;
using NLog;
using System.Net;
using System.Windows.Forms;
using HtmlAgilityPack;

namespace ShiPanEStockTrader
{
    /// <summary>
    /// Client classification(For Foundersc Security Only)
    /// </summary>
    public enum FzzqClientClass
    {
        /// <summary>
        /// 未知
        /// </summary>
        NotSet = -1,
        /// <summary>
        /// 普客户
        /// </summary>
        NormalClietn = 1,
        /// <summary>
        /// 富泉友
        /// </summary>
        RichClient = 2,
        /// <summary>
        /// 贵泉友
        /// </summary>
        NobalClient = 3,
        /// <summary>
        /// 雅泉友
        /// </summary>
        ElegantClient = 4
    }

    /// <summary>
    /// Stock Exchange
    /// </summary>
    public enum StockExchange
    {
        NotSet = -1,
        /// <summary>
        /// 上海
        /// </summary>
        Shanghai = 0,
        /// <summary>
        /// 深圳
        /// </summary>
        Shenzhen = 1
    }

    /// <summary>
    /// Order Type
    /// </summary>
    public enum ShiPanEOrderPriceType
    {
        /// <summary>
        /// 限价委托
        /// </summary>
        Limit=0,
        /// <summary>
        /// 对手方最优价格委托
        /// </summary>
        Best_Of_Rivel=1,
        /// <summary>
        /// 本方最优价格委托
        /// </summary>
        Best_Of_Ally=2,
        /// <summary>
        /// 即时成交剩余撤销委托
        /// </summary>
        Fill_Or_Kill=3,
        /// <summary>
        /// 五档即时成交剩余撤销
        /// </summary>
        Fill_Five_Price_And_Kill=4,
        /// <summary>
        /// 全额成交或撤销委托
        /// </summary>
        Fill_All_Or_Kill=5,
        /// <summary>
        /// 五档即时成交剩余转限
        /// </summary>
        Fill_And_To_Limit = 6
    }

    /// <summary>
    /// Constant list that is supported by amountProportion
    /// </summary>
    public enum ShiPanEOrderamountProportionConstant
    {
        /// <summary>
        /// 全部
        /// </summary>
        ALL=0,
        /// <summary>
        /// 1/2
        /// </summary>
        Half=1,
        /// <summary>
        /// 1/3
        /// </summary>
        One_Third=2,
        /// <summary>
        /// 1/4
        /// </summary>
        One_Forth=3,
        /// <summary>
        /// 1/5
        /// </summary>
        One_Fifth=4
    }

    /// <summary>
    /// Status list of orders
    /// </summary>
    public enum ShiPanEOrderStatus
    {
        /// <summary>
        /// 未知
        /// </summary>
        NotSet = -1,
        /// <summary>
        /// 已报
        /// </summary>
        Sent = 0,
        /// <summary>
        /// 部成
        /// </summary>
        PartiallyFilled = 1,
        /// <summary>
        /// 已成
        /// </summary>
        Filled = 2,
        /// <summary>
        /// 已撤
        /// </summary>
        Cancelled = 3,
        /// <summary>
        /// 废单
        /// </summary>
        Rejected = 4,
        /// <summary>
        /// 未报
        /// </summary>
        NotSent = 5,
        /// <summary>
        /// 已报待撤
        /// </summary>
        SentButNotCanceled=6
    }

    /// <summary>
    /// struct for building the order to place
    /// </summary>
    public struct ShiPanEOrder
    {
        public string action;
        public string symbol;
        public string type;
        public ShiPanEOrderPriceType priceType;
        public double price;
        public int amount;
        public ShiPanEOrderamountProportionConstant amountProportion;
    }

    /// <summary>
    /// Response value of GetOrders()
    /// </summary>
    public struct ShiPanEOrderStatusRespond
    {
        /// <summary>
        /// 委托时间,format in "HHmmss"
        /// </summary>
        public string time;
        /// <summary>
        /// 证券代码
        /// </summary>
        public int label;
        /// <summary>
        /// 证券名称
        /// </summary>
        public string name;
        /// <summary>
        /// 买卖标志
        /// </summary>
        public string BuyOrSell;
        /// <summary>
        /// 委托类型
        /// </summary>
        public string OrderMark;
        /// <summary>
        /// 委托状态
        /// </summary>
        public ShiPanEOrderStatus status;
        /// <summary>
        /// 委托价格
        /// </summary>
        public float price;
        /// <summary>
        /// 委托数量
        /// </summary>
        public int amount;
        /// <summary>
        /// 委托代码
        /// </summary>
        public int orderNum;
        /// <summary>
        /// 成交价格
        /// </summary>
        public float FillPrice;
        /// <summary>
        /// 成交数量
        /// </summary>
        public int FillAmount;
        /// <summary>
        /// 报价方式
        /// </summary>
        public string WayOfPricing;
        /// <summary>
        /// 股东代号
        /// </summary>
        public string StockAccount;
    }

    /// <summary>
    /// Response value of GetPositions()
    /// </summary>
    public struct ShiPanEStockPosition
    {
        /// <summary>
        /// 证券代码
        /// </summary>
        public int SecurityLabel;
        /// <summary>
        /// 证券名称
        /// </summary>
        public string SecurityName;
        /// <summary>
        /// 证券数量
        /// </summary>
        public float SecurityAmount;
        /// <summary>
        /// 可用数量
        /// </summary>
        public float AmountSellable;
        /// <summary>
        /// 成本价
        /// </summary>
        public float Cost;
        /// <summary>
        /// 当前价格
        /// </summary>
        public float LastestPrice;
        /// <summary>
        /// 当前市值
        /// </summary>
        public float LastTotalValue;
        /// <summary>
        /// 盈亏总数
        /// </summary>
        public float Profit;
        /// <summary>
        /// 盈亏比例(%)
        /// </summary>
        public float ProfitPercentage;
        /// <summary>
        /// 股东账号
        /// </summary>
        public string StockAccountNum;
        /// <summary>
        /// 终止日期
        /// </summary>
        public string EndDateofTrading;

    }

    /// <summary>
    /// Struct that stores asset info of the account
    /// </summary>
    public struct AccountAssetInfo
    {
        /// <summary>
        /// 账户余额
        /// </summary>
        public float CashBalance;
        /// <summary>
        /// 证券市值
        /// </summary>
        public float TotalSecurityValue;
        /// <summary>
        /// 可取资金
        /// </summary>
        public float CanWithdraw;
        /// <summary>
        /// 可用资金
        /// </summary>
        public float CanUse;
        /// <summary>
        /// 参考盈亏
        /// </summary>
        public float Profit;
        /// <summary>
        /// 账户总资产
        /// </summary>
        public float TotalAsset;
        /// <summary>
        /// Time of Last update
        /// </summary>
        public DateTime LastUpdateTime;
    }

    /// <summary>
    /// Basic client info struct
    /// </summary>
    public struct ShiPanEAccountInfo
    {
        FzzqClientClass clientclass;
        string clientID;
        string clientName;
        string BrokerBranch;
    }

    /// <summary>
    /// Main class that carries all the method
    /// </summary>
    public class ShiPanETrader
    {
        private static Dictionary<string, ShiPanEOrderStatus> dic_ForShiPanEOrderStatus = new Dictionary<string, ShiPanEOrderStatus>
        {
            {"已报",ShiPanEOrderStatus.Sent },
            {"未报", ShiPanEOrderStatus.NotSent},
            {"部成",ShiPanEOrderStatus.PartiallyFilled },
            {"已成",ShiPanEOrderStatus.Filled },
            {"废单",ShiPanEOrderStatus.Rejected },
            {"已撤",ShiPanEOrderStatus.Cancelled},
            {"已报待撤",ShiPanEOrderStatus.SentButNotCanceled }
        };
        ///Basic Logger
        private static Logger NLogger = LogManager.GetCurrentClassLogger();
        ///RestClient used through the lifespan
        private RestClient rclient;
        private string connString;
        ///Member to record account asset,updated by GetPositions().
        public AccountAssetInfo AccountAsset = new AccountAssetInfo();

        /// <summary>
        /// Initialize ShiPanETrader.
        /// </summary>
        /// <param name="host">Address of the ShiPanE host.IP and Hostname both work.</param>
        /// <param name="Port">(Optional)Port Number of ShiPanE.Default is 8888.</param>
        /// <param name="clientID">(Optional)If multiple TdxW exist, use this as an identifier</param>
        /// <returns>0 = good;-1 = bad.</returns>
        public int SetTdxwHost(string host, int Port = 8888, string clientID="")
        {
            connString = host + ":" + Port.ToString();
            if (!host.ToLower().Contains("http"))
                connString = "http://" + connString;
            rclient = new RestClient(connString);
            var request = new RestRequest("statuses", Method.GET);
            if(clientID.Length>0)
                request.AddParameter("client", clientID);
            IRestResponse res = rclient.Execute(request);
            if (res.ResponseStatus == ResponseStatus.Completed
                && res.Content == "\"运行正常\"")
                return 0;
            else
                return -1;
        }

        /// <summary>
        /// Gets the owner of current account and his Account No.
        /// </summary>
        /// <returns>Raw Json String</returns>
        public string GetAccountInfo()
        {
            var request = new RestRequest("accounts", Method.GET);
            IRestResponse res = rclient.Execute(request);
            return res.Content;
        }

        /// <summary>
        /// Get all the positions and account asset. Positions will be returned and AccountAsset member is changed in this class.
        /// </summary>
        /// <returns>List of Position in ShiPanEStockPostion struct</returns>
        public List<ShiPanEStockPosition> GetPositions()
        {
            //Declaire return value
            List<ShiPanEStockPosition> positions = new List<ShiPanEStockPosition>();
            //Build REST Request
            var request = new RestRequest("positions", Method.GET);
            IRestResponse res = rclient.Execute(request);

            //If respond state is not completed,return null
            if (res.ResponseStatus!=ResponseStatus.Completed)
                return null;
            //Root Json object
            JObject DeSerialized_Json_Object = JObject.Parse(res.Content);

            //Update this.AccountAsset
            JToken accountinfotoken = DeSerialized_Json_Object["subAccounts"]["人民币"];
            AccountAsset.CashBalance = float.Parse(accountinfotoken["余额"].ToString());
            AccountAsset.TotalSecurityValue = float.Parse(accountinfotoken["参考市值"].ToString());
            AccountAsset.CanWithdraw = float.Parse(accountinfotoken["可取"].ToString());
            AccountAsset.CanUse = float.Parse(accountinfotoken["可用"].ToString());
            AccountAsset.Profit = float.Parse(accountinfotoken["盈亏"].ToString());
            AccountAsset.TotalAsset = float.Parse(accountinfotoken["资产"].ToString());
            AccountAsset.LastUpdateTime = DateTime.Now;

            //Get count of positions
            int length = int.Parse(DeSerialized_Json_Object["count"].ToString());
            if (length == 0)
                return null;

            //Build Position & Add to list
            IList<JToken> tokens = DeSerialized_Json_Object["dataTable"]["rows"].Children().ToList();
            foreach (JToken token in tokens)
            {
                try
                {
                    IList<JToken> SubRowToken = token.Children().ToList();
                    ShiPanEStockPosition position_to_add = new ShiPanEStockPosition();
                    position_to_add.SecurityLabel = int.Parse(SubRowToken[0].ToString());
                    position_to_add.SecurityName = SubRowToken[1].ToString();
                    position_to_add.SecurityAmount = float.Parse(SubRowToken[2].ToString());
                    position_to_add.AmountSellable = float.Parse(SubRowToken[3].ToString());
                    position_to_add.Cost = float.Parse(SubRowToken[4].ToString());
                    position_to_add.LastestPrice = float.Parse(SubRowToken[5].ToString());
                    position_to_add.LastTotalValue = float.Parse(SubRowToken[6].ToString());
                    position_to_add.Profit = float.Parse(SubRowToken[7].ToString());
                    position_to_add.ProfitPercentage = float.Parse(SubRowToken[8].ToString());
                    position_to_add.StockAccountNum = SubRowToken[9].ToString();
                    positions.Add(position_to_add);
                }
                catch (Exception ex)
                {
                    NLogger.Debug(ex.Message.ToString());
                }
            }
            return positions;
        }

        /// <summary>
        /// Place order through ShiPanE Api，return the order number
        /// </summary>
        /// <param name="label">Label like 600256</param>
        /// <param name="BuyOrSell">"BUY" or "SELL"</param>
        /// <param name="OrderPriceType">Order price type defined in ShiPanEOrderPriceType</param>
        /// <param name="price">Order price</param>
        /// <param name="amount">Order amount</param>
        /// <returns></returns>
        public int PlaceOrder(string label,string BuyOrSell,ShiPanEOrderPriceType OrderPriceType, double price,int amount)
        {
            //Build ShiPanEOrder
            ShiPanEOrder order_to_place = new ShiPanEOrder();
            order_to_place.action = BuyOrSell.ToUpper();
            order_to_place.symbol = label;
            order_to_place.priceType = OrderPriceType;
            if (order_to_place.priceType == ShiPanEOrderPriceType.Limit)
                order_to_place.type = "LIMIT";
            else
                order_to_place.type = "MARKET";
            order_to_place.price = price;
            order_to_place.amount = amount;

            //Build object from parameters
            object order = new
            {
                action = order_to_place.action,
                symbol = order_to_place.symbol,
                type = order_to_place.type,
                priceType = (int)order_to_place.priceType,
                price = order_to_place.price,
                amount = order_to_place.amount
            };

            //Build Rest Request
            var rest_order_request = new RestRequest("orders", Method.POST);
            rest_order_request.RequestFormat = DataFormat.Json;
            rest_order_request.AddBody(order);

            IRestResponse res = rclient.Execute(rest_order_request);
            if (res.ResponseStatus == ResponseStatus.Completed
                && res.StatusCode == System.Net.HttpStatusCode.OK)
            {
                JObject responseboj = JObject.Parse(res.Content);
                JToken thistoken = responseboj["id"];
                return int.Parse(thistoken.ToString());
            }
            else return -1;
        }
           
        /// <summary>
        /// Basic method of get_orders
        /// </summary>
        /// <param name="para"></param>
        /// <param name="value"></param>
        /// <returns>A list of order</returns>
        private List<ShiPanEOrderStatusRespond> GetOrders(string para = "", string value = "")
        {
            //Build Request
            var request = new RestRequest("orders", Method.GET);
            if (para.Length > 0 && value.Length > 0)
                request.AddParameter(para, value);
            IRestResponse res = rclient.Execute(request);

            //Get and Check response
            if (res.ResponseStatus == ResponseStatus.Completed)
            {
                //Build Json Root Object
                JObject Root_JSon_Obj = JObject.Parse(res.Content);
                int Rows_count = int.Parse(Root_JSon_Obj["count"].ToString());
                if (Rows_count == 0)
                    return null;
                List<ShiPanEOrderStatusRespond> responses = new List<ShiPanEOrderStatusRespond>();
                List<JToken> tokenList = Root_JSon_Obj["dataTable"]["rows"].Children().ToList();
                foreach (JToken token in tokenList)
                {
                    ShiPanEOrderStatusRespond tmpOrderStatus = new ShiPanEOrderStatusRespond();
                    string timestring= token[0].ToString();
                    //Align time string to HHmmss format
                    if (timestring.Length == 5)
                        timestring = "0" + timestring;
                    tmpOrderStatus.time = timestring;
                    tmpOrderStatus.label = int.Parse(token[1].ToString());
                    tmpOrderStatus.name = token[2].ToString();
                    tmpOrderStatus.BuyOrSell = token[3].ToString();
                    tmpOrderStatus.OrderMark = token[4].ToString();
                    tmpOrderStatus.status = dic_ForShiPanEOrderStatus[token[5].ToString()];
                    tmpOrderStatus.price = float.Parse(token[6].ToString());
                    tmpOrderStatus.amount = (int)float.Parse(token[7].ToString());
                    tmpOrderStatus.orderNum = int.Parse(token[8].ToString());
                    tmpOrderStatus.FillPrice = float.Parse(token[9].ToString());
                    tmpOrderStatus.FillAmount = (int)float.Parse(token[10].ToString());
                    tmpOrderStatus.WayOfPricing = token[11].ToString();
                    tmpOrderStatus.StockAccount = token[12].ToString();
                    responses.Add(tmpOrderStatus);
                }
                return responses;
            }
            else
                return null;
        }

        /// <summary>
        /// Get all the orders placed.
        /// </summary>
        /// <returns>A list of order</returns>
        public List<ShiPanEOrderStatusRespond> GetOrders()
        {
            return GetOrders("", "");
        }

        /// <summary>
        /// Get all filled orders.
        /// </summary>
        /// <returns>A list of filled order</returns>
        public List<ShiPanEOrderStatusRespond> GetFilledOrders()
        {
            return GetOrders("type", "filled");
        }

        /// <summary>
        /// Get all cancelable orders.
        /// </summary>
        /// <returns>A list of cancelable order</returns>
        public List<ShiPanEOrderStatusRespond> GetOpenOrders()
        {
            return GetOrders("type", "open");
        }

        /// <summary>
        /// Cancel the order identified by OrderNum
        /// </summary>
        /// <param name="orderNum">The number of order that you want to cancel</param>
        /// <returns>Number of order you want to cancel.</returns>
        public string CancelOrder(int orderNum)
        {
            var request = new RestRequest("orders/" + orderNum.ToString(), Method.DELETE);
            IRestResponse res = rclient.Execute(request);
            return res.Content;
        }

        /// <summary>
        /// Cancel all unfilled orders
        /// </summary>
        /// <returns></returns>
        public string CancelAllOrders()
        {
            var request = new RestRequest("orders", Method.DELETE);
            IRestResponse res = rclient.Execute(request);
            return res.Content;
        }

		/// <summary>
		/// Gets the new stock list for a certain date
		/// </summary>
		/// <returns>An arrary of string that represents the new stock list</returns>
		/// <param name="date">Date of the new stock</param>
        public string[] GetNewStockList(DateTime date)
        {
            //Dictionary<String of Date, Label of stock>
            Dictionary<string, List<string>> NewStockDictionary = new Dictionary<string, List<string>>(); 

            //Obtain raw HTML string using WebClient()
            WebClient NewStockWebClient = new WebClient();
            NewStockWebClient.Credentials = CredentialCache.DefaultCredentials;
            byte[] rawHTML;
            try
            {
                rawHTML = NewStockWebClient.DownloadData("http://money.finance.sina.com.cn/corp/view/vRPD_NewStockIssue.php");
            }catch(Exception ex)
            {
                NLogger.Debug("Obtain new stock info from Sina failed. Time:{0} Error:{1}", DateTime.Now, ex.Message.ToString());
                return null;
            }
            string pageHTML = Encoding.Default.GetString(rawHTML);

            //Parse the 
            HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
            doc.LoadHtml(pageHTML);           
            HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//table")[0].SelectNodes("tr");
            foreach (HtmlNode row in nodes)
            {
                int headchecker = 0;
                HtmlNodeCollection cells = row.SelectNodes("td");
                if(cells.Count > 4 && int.TryParse(cells[0].InnerText.Trim(),out headchecker))
                {
                    //Line valid
                    //Buy price is cells[7]
                    string StockInfo = string.Format("{0}/{1}", cells[1].InnerText.Trim(), cells[7].InnerText.Trim());
                    string ApplicationDate = cells[3].InnerText.Trim();
                    if (!NewStockDictionary.ContainsKey(ApplicationDate))
                        NewStockDictionary.Add(ApplicationDate, new List<string>());
                    NewStockDictionary[ApplicationDate].Add(StockInfo);
                }
            }
            if (NewStockDictionary.ContainsKey(date.ToString("yyyy-MM-dd")))
                return NewStockDictionary[date.ToString("yyyy-MM-dd")].ToArray();
            return null;
        }

		/// <summary>
		/// Gets the new stock list for today.
		/// </summary>
		/// <returns>The new stock list today.</returns>
		public string[] GetNewStockListToday()
		{
			return GetNewStockList(DateTime.Now);
		}
    }
}

